package Phase1.RegexParser;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Scanner;
import Phase1.NFAHandler.*;
import Phase1.TableDFABuilder.NFASetNode;
import Phase1.TableDFABuilder.NFAtoDFAConverter;

/**
 * Created with IntelliJ IDEA.
 * User: Alvin
 * Date: 11/11/12
 * Time: 6:16 AM
 * I'm not bothering commenting all these functions, because they all mirror the structure names given in Project1-Regex.doc.
 */
public class RegexFileParser {

    private HashMap<String, CC> _charClasses;
    private HashMap<String, TD> _tokenDefinitions;
    private String _input;

    /**
     * Constructs a new RegexFileParser on a given input file
     * @param inputFile
     */
    public RegexFileParser(String inputFile) throws Exception {
        _charClasses = new HashMap<String, CC>();
        _tokenDefinitions = new HashMap<String, TD>();
        int state = 0;
        File file = new File(inputFile);
        try {
            Scanner in = new Scanner(file);
            while(in.hasNextLine()) {
                String line = in.nextLine();
                if(line.length() == 0) {
                    state++;
                }
                else if(line.length() > 0 && state == 0) {
                    ProcessCC(line);
                }
                else if(line.length() > 0 && state == 1) {
                    ProcessTD(line);
                }
            }
            Iterator it = _tokenDefinitions.keySet().iterator();
            while(it.hasNext()) {
                TD temp = _tokenDefinitions.get(it.next());
                temp.head = NFAtoDFAConverter.ConvertNFAtoDFA(temp.nfa);
            }
        }
        catch (IOException e) {
            System.out.println(e.getMessage());
        }
    }

    public RegexFileParser(String inputFile, String regex) throws Exception {
        _charClasses = new HashMap<String, CC>();
        _tokenDefinitions = new HashMap<String, TD>();
        _input = regex;
        _tokenDefinitions.put("TOKEN", new TD(regex()));
        Iterator it = _tokenDefinitions.keySet().iterator();
        while(it.hasNext()) {
            TD temp = _tokenDefinitions.get(it.next());
            temp.head = NFAtoDFAConverter.ConvertNFAtoDFA(temp.nfa);
        }
    }

    public String MatchToken(String token) {
        Iterator it = _tokenDefinitions.keySet().iterator();
        while(it.hasNext()) {
            boolean failed = false;
            String tokenName = (String)(it.next());
            TD tokenDef = _tokenDefinitions.get(tokenName);
            NFASetNode placeHolder = tokenDef.head;
            if(token.length() == 0) {
                if (tokenDef.nfa.head != tokenDef.nfa.tail) {
                    return null;
                }
            }
            for(int i=0; i<token.length(); i++) {
                String c = token.charAt(i)+"";
                Iterator it2 = placeHolder.transitions.keySet().iterator();
                boolean advanced = false;
                while(it2.hasNext()) {
                    String trans = (String)(it2.next());
                    if(_charClasses.containsKey(trans)) {
                        CC charClass = _charClasses.get(trans);
                        if(charClass.match(c)) {
                            placeHolder = placeHolder.transitions.get(trans);
                            advanced = true;
                        }
                    }
                    else {
                        if(trans.indexOf(c) > -1) {
                            placeHolder = placeHolder.transitions.get(trans);
                            advanced = true;
                        }
                    }
                }
                if(!advanced) {
                    failed = true;
                }
            }
            //if(!failed && placeHolder.nodes.contains(tokenDef.nfa.tail)) {
            if(!failed) {
                return tokenName;
            }
        }
        return null;
    }

    public String MatchTokenExclusive(String token) {
        Iterator it = _tokenDefinitions.keySet().iterator();
        while(it.hasNext()) {
            boolean failed = false;
            String tokenName = (String)(it.next());
            TD tokenDef = _tokenDefinitions.get(tokenName);
            NFASetNode placeHolder = tokenDef.head;
            if(token.length() == 0) {
                if (tokenDef.nfa.head != tokenDef.nfa.tail) {
                    return null;
                }
            }
            for(int i=0; i<token.length(); i++) {
                String c = token.charAt(i)+"";
                Iterator it2 = placeHolder.transitions.keySet().iterator();
                boolean advanced = false;
                while(it2.hasNext()) {
                    String trans = (String)(it2.next());
                    if(_charClasses.containsKey(trans)) {
                        CC charClass = _charClasses.get(trans);
                        if(charClass.match(c)) {
                            placeHolder = placeHolder.transitions.get(trans);
                            advanced = true;
                        }
                    }
                    else {
                        if(trans.indexOf(c) > -1) {
                            placeHolder = placeHolder.transitions.get(trans);
                            advanced = true;
                        }
                    }
                }
                if(!advanced) {
                    failed = true;
                }
            }
            if(!failed && placeHolder.nodes.contains(tokenDef.nfa.tail)) {
            //if(!failed) {
                return tokenName;
            }
        }
        return null;
    }

    /**
     * Creates a character class given a definition string
     * @param line
     */
    public void ProcessCC(String line) {
        Scanner scan = new Scanner(line);
        String name = scan.next().trim();
        String value = scan.nextLine().trim();
        CC temp;
        if(value.substring(0,2).equals("[^")) {
            value = value.substring(2);
            while(value.indexOf('-') > -1) {
                int left = value.indexOf('-') - 1;
                int right = value.indexOf('-') + 1;
                int leftChar = value.charAt(left);
                int rightChar = value.charAt(right);
                String sub = "";
                for(int i=leftChar; i<=rightChar; i++) {
                    sub += (char)i;
                }
                value = value.substring(0,left) + sub + value.substring(right+1);
            }
            int finalDollarIndex = -1;
            int finalCloseBracketIndex = -1;
            for(int i=0; i<value.length(); i++) {
                if(value.charAt(i) == '$') {
                    finalDollarIndex = i;
                }
                if(value.charAt(i) == ']') {
                    finalCloseBracketIndex = i;
                }
            }
            String ccName = value.substring(finalDollarIndex);
            value = value.substring(0,finalCloseBracketIndex);
            String ccValue = _charClasses.get(ccName).Value;
            for(int i=0; i<value.length(); i++) {
                ccValue = ccValue.replace(value.charAt(i)+"","");
            }
            value = ccValue;
            temp = new CC(value);
        }
        else {
            while(value.indexOf('-') > -1) {
                int left = value.indexOf('-') - 1;
                int right = value.indexOf('-') + 1;
                int leftChar = value.charAt(left);
                int rightChar = value.charAt(right);
                String sub = "";
                for(int i=leftChar; i<=rightChar; i++) {
                    sub += (char)i;
                }
                value = value.substring(0,left) + sub + value.substring(right+1);
            }
            temp = new CC(value.substring(1,value.length()-1));
        }
        _charClasses.put(name,temp);
    }

    public void ProcessTD(String line) throws Exception {
        Scanner scan = new Scanner(line);
        String name = scan.next().trim();
        _input = scan.nextLine().trim();
        /*for(int i=0; i<_input.length(); i++) {
            if(_input.charAt(i) == '\\') {
                if(_input.charAt(i+1) == '\\' || _input.charAt(i+1) == '^' || _input.charAt(i+1) == '-' || _input.charAt(i+1) == '[' || _input.charAt(i+1) == ']' || _input.charAt(i+1) == '*' || _input.charAt(i+1) == '+' || _input.charAt(i+1) == '.') {
                    _input = _input.substring(0,i) + _input.substring(i+1);
                }
            }
        }*/
        _tokenDefinitions.put(name, new TD(regex()));
    }

    private String peek() {
        String output = null;
        if(_input.charAt(0) == '\\') {
            output = _input.substring(0,2);
        }
        for (String name : _charClasses.keySet()) {
            if(output == null &&_input.indexOf(name) == 0) {
                output = name;
            }
        }
        if(output == null) {
            output = _input.substring(0,1);
        }
        return output;
    }

    private void match(String token) throws Exception {
        if(peek().equals(token)) {
            _input = _input.substring(token.length());
            _input = _input.trim();
        }
        else {
            throw new Exception("Failed to match " + token);
        }
    }

    private boolean more() {
        if(_input.length() > 0) {
            return true;
        }
        return false;
    }

    public NFA regex() throws Exception {
        NFA out = rexp();
//        if(_input.length() > 0) {
//            throw new Exception();
//        }
        return out;
    }

    public NFA rexp() throws Exception {
        NFA a = rexp1();
        NFA b = rexpp(a);
        return b;
    }

    public NFA rexpp(NFA sibling) throws Exception {
        if(more() && peek().equals("|")) {
            match("|");
            NFA a = rexp1();
            NFA out = new NFA();
            out.head = new NFANode();
            out.tail = new NFANode();
            ArrayList<NFANode> branch = new ArrayList<NFANode>();
            branch.add(sibling.head);
            branch.add(a.head);
            out.head.transitions.put("epsilon",branch);
            ArrayList<NFANode> sibEnd = sibling.tail.transitions.get("epsilon");
            if(sibEnd == null) {
                sibEnd = new ArrayList<NFANode>();
            }
            if(!sibEnd.contains(out.tail)) {
                sibEnd.add(out.tail);
            }
            sibling.tail.transitions.put("epsilon",sibEnd);
            ArrayList<NFANode> aEnd = a.tail.transitions.get("epsilon");
            if(aEnd == null) {
                aEnd = new ArrayList<NFANode>();
            }
            if(!aEnd.contains(out.tail)) {
                aEnd.add(out.tail);
            }
            a.tail.transitions.put("epsilon",aEnd);
            out = rexpp(out);
            return out;
        }
        else {
            return sibling;
        }
    }

    public NFA rexp1() throws Exception {
        NFA a = rexp2();
        NFA b = rexp1p();
        NFA out = CombineNFAs(a,b);
        return out;
    }

    public NFA rexp1p() throws Exception {
        if(more() && (peek().equals("(") || isRE_CHAR(peek()) || isCharClass(peek()))) {
            NFA a = rexp2();
            NFA b = rexp1p();
            NFA out = CombineNFAs(a,b);
            return out;
        }
        else {
            return null;
        }
    }

    public NFA rexp2() throws Exception {
        if(more()) {
            if(peek().equals("(")) {
                match("(");
                NFA out = rexp();
                match(")");
                rexp2tail(out);
                return out;
            }
            else if(isRE_CHAR(peek())) {
                String token = peek();
                match(token);
                NFA out = GeneratePrimitiveNFA(token);
                rexp2tail(out);
                return out;
            }
            else {
                NFA out = rexp3();
                return out;
            }
        }
        else {
            return null;
        }
    }

    public void rexp2tail(NFA parent) throws Exception {
        if(more() && peek().equals("*")) {
            match("*");
            ArrayList<NFANode> tailToHead = parent.tail.transitions.get("epsilon");
            if(tailToHead == null) {
                tailToHead = new ArrayList<NFANode>();
            }
            if(!tailToHead.contains(parent.head)) {
                tailToHead.add(parent.head);
            }
            ArrayList<NFANode> headToTail = parent.head.transitions.get("epsilon");
            if(headToTail == null) {
                headToTail = new ArrayList<NFANode>();
            }
            if(!headToTail.contains(parent.tail)) {
                headToTail.add(parent.tail);
            }
            parent.head.transitions.put("epsilon",headToTail);
            parent.tail.transitions.put("epsilon",tailToHead);
        }
        else if(more() && peek().equals("+")) {
            match("+");
            ArrayList<NFANode> tailToHead = parent.tail.transitions.get("epsilon");
            if(tailToHead == null) {
                tailToHead = new ArrayList<NFANode>();
            }
            if(!tailToHead.contains(parent.head)) {
                tailToHead.add(parent.head);
            }
            parent.tail.transitions.put("epsilon",tailToHead);
        }
    }

    public NFA rexp3() throws Exception {
        if(more() && (peek().equals(".") || isCLS_CHAR(peek()) || peek().equals("[") || peek().equals("^") || _charClasses.keySet().contains(peek()))) {
            NFA out = charclass();
            return out;
        }
        else {
            return null;
        }
    }

    public NFA charclass() throws Exception {
        if(more()) {
            if(peek().equals(".")) {
                String token = peek();
                match(token);
                NFA out = GeneratePrimitiveNFA(token);
                return out;
            }
            else {
                if(peek().equals("[")) {
                    match("[");
                    NFA out = charclass1();
                    return out;
                }
                else {
                    NFA out = definedclass();
                    return out;
                }
            }
        }
        else {
            throw new Exception();
        }
    }

    public NFA charclass1() throws Exception {
        if(more()) {
            if(isCLS_CHAR(peek()) || peek().equals("]")) {
                NFA out = charsetlist();
                return out;
            }
            else {
                NFA out = excludeset();
                return out;
            }
        }
        else {
            throw new Exception();
        }
    }

    public NFA charsetlist() throws Exception {
        if(more()) {
            if(isCLS_CHAR(peek())) {
                NFA a = charset();
                NFA b = charsetlist();
                if(b == null) {
                    return a;
                }
                else {
                    NFA out = new NFA();
                    out.head = new NFANode();
                    out.tail = new NFANode();
                    ArrayList<NFANode> branch = new ArrayList<NFANode>();
                    branch.add(a.head);
                    branch.add(b.head);
                    out.head.transitions.put("epsilon",branch);
                    ArrayList<NFANode> aEnd = a.tail.transitions.get("epsilon");
                    if(aEnd == null) {
                        aEnd = new ArrayList<NFANode>();
                    }
                    if(!aEnd.contains(out.tail)) {
                        aEnd.add(out.tail);
                    }
                    a.tail.transitions.put("epsilon",aEnd);
                    ArrayList<NFANode> bEnd = b.tail.transitions.get("epsilon");
                    if(bEnd == null) {
                        bEnd = new ArrayList<NFANode>();
                    }
                    if(!bEnd.contains(out.tail)) {
                        bEnd.add(out.tail);
                    }
                    b.tail.transitions.put("epsilon",bEnd);
                    return out;
                }
                //NFA out = CombineNFAs(a,b);
            }
            else {
                match("]");
                return null;
            }
        }
        else {
            throw new Exception();
        }
    }

    public NFA charset() throws Exception {
        if(more()) {
            if(isCLS_CHAR(peek())) {
                String token = peek();
                match(token);
                //NFA a = GeneratePrimitiveNFA(token);
                NFA out;
                if(more() && peek().equals("-")) {
                    out = charsettail(token);
                }
                else {
                    out = GeneratePrimitiveNFA(token);
                }
                return out;
            }
        }
        else {
            throw new Exception();
        }
        return null;
    }

    public NFA charsettail(String startingCLSChar) throws Exception {
        if(more() && peek().equals("-")) {
            String tokenA = peek();
            match(tokenA);
            if(more() && isCLS_CHAR(peek())) {
                String tokenB = peek();
                match(tokenB);
                int leftChar = startingCLSChar.charAt(0);
                int rightChar = tokenB.charAt(0);
                String sub = "";
                for(int i=leftChar; i<=rightChar; i++) {
                    sub += (char)i;
                }
                NFA out = GeneratePrimitiveNFA(sub);
                return out;
            }
            else {
                throw new Exception();
            }
        }
        else {
            return null;
        }
    }

    public NFA excludeset() throws Exception {
        if(more()) {
            if(peek().equals("^")) {
                match("^");
                NFA a = GeneratePrimitiveNFA("^");
                NFA b = charset();
                match("]");
                NFA c = GeneratePrimitiveNFA("]");
                match("I");
                NFA d = GeneratePrimitiveNFA("I");
                match("N");
                NFA e = GeneratePrimitiveNFA("N");
                NFA f = excludesettail();
                NFA out = CombineNFAs(a,b);
                out = CombineNFAs(out,c);
                out = CombineNFAs(out,d);
                out = CombineNFAs(out,e);
                out = CombineNFAs(out,f);
                return out;
            }
        }
        else {
            throw new Exception();
        }
        return null;
    }

    public NFA excludesettail() throws Exception {
        if(more()) {
            if(peek().equals("[")) {
                match("[");
                NFA a = GeneratePrimitiveNFA("[");
                NFA b = charset();
                match("]");
                NFA c = GeneratePrimitiveNFA("]");
                NFA out = CombineNFAs(a,b);
                out = CombineNFAs(out,c);
                return out;
            }
            else {
                NFA out = definedclass();
                return out;
            }
        }
        else {
            throw new Exception();
        }
    }

    public NFA definedclass() throws Exception {
        if(!_charClasses.keySet().contains(peek())) {
            throw new Exception();
        }
        else {
            String token = peek();
            match(token);
            NFA out = GeneratePrimitiveNFA(token);
            return out;
        }
    }

    public boolean isRE_CHAR(String in) {
        /*RE_CHAR is the union of two sets:
              1) the ASCII printable characters other than space, \, *, +, ?, |, [, ], (, ), ., ' and "
              2) escaped characters: \ (backslash space), \\, \*, \+, \?, \|, \[, \], \(, \), \., \' and \"
              decimal codes for printable characters are 32, 92, 42, 43, 63, 124, 91, 93, 40, 41, 46, 39, and 34 respectively.
          */
        if(in.length() == 1) {
            char c = in.charAt(0);
            if(c >= 33 || c <=126) {
                if(!(c == 34 || (c >= 39 && c <= 43) || c == 46 || c == 63 || (c >= 91 && c <=93) || c == 124)) {
                    return true;
                }
            }
        }
        else if(in.length() == 2) {
            char c = in.charAt(0);
            if(c == 92) {
                char c2 = in.charAt(1);
                if(c2 == 32 || c2 == 34 || (c2 >= 39 && c2 <= 43) || c2 == 46 || c2 == 63 || (c2 >= 91 && c2 <=93) || c2 == 124) {
                    return true;
                }
            }
        }
        return false;
    }

    public boolean isCLS_CHAR(String in) {
        /*CLS_CHAR is the union of two sets:
              1) the ASCII printable characters (see http://en.wikipedia.org/wiki/ASCII#ASCII_printable_characters) other than \, ^, -, [ and ]
              2) escaped characters: \\, \^, \-, \[ and \]
              decimal codes for printable characters are 92, 94, 45, 91, 93 respectively.
          */
        if(in.length() == 1) {
            char c = in.charAt(0);
            if(c >= 33 || c <=126) {
                if(!(c == 45 || (c >= 91 && c <= 94))) {
                    return true;
                }
            }
        }
        else if(in.length() == 2) {
            char c = in.charAt(0);
            if(c == 92) {
                char c2 = in.charAt(1);
                if(c2 == 45 || (c2 >= 91 && c2 <= 94)) {
                    return true;
                }
            }
        }
        return false;
    }

    public boolean isCharClass(String token) {
        Iterator i = _charClasses.keySet().iterator();
        String charClass;
        while(i.hasNext()) {
            charClass = (String)i.next();
            if(charClass.equals(token)) {
                return true;
            }
        }
        return false;
    }

    public NFA GeneratePrimitiveNFA(String symbol) {
        NFA nfa = new NFA();
        nfa.head = new NFANode();
        nfa.tail = new NFANode();
        ArrayList<NFANode> end = new ArrayList<NFANode>();
        end.add(nfa.tail);
        symbol = symbol.replace("\\","");
        nfa.head.transitions.put(symbol,end);
        return nfa;
    }

    public NFA CombineNFAs(NFA a, NFA b) {
        if(a == null && b == null) {
            return null;
        }
        else if(a == null) {
            return b;
        }
        else if(b == null) {
            return a;
        }
        else {
            ArrayList<NFANode> endA = a.tail.transitions.get("epsilon");
            if(endA == null) {
                endA = new ArrayList<NFANode>();
            };
            endA.add(b.head);
            a.tail.transitions.put("epsilon",endA);
            a.tail = b.tail;
            return a;
        }
    }
}
